import HttpManager from "../Service/http/HttpManager";
import { ItemResIcon } from "../View/Item/ItemResIcon";
import PreciseUtil from "./PreciseUtil";
import { LoadUtil } from "./LoadUtil";
import Message from "../Common/Message";
import { EBundleType, EResType } from "../Common/Enum";
import EventID from "../Common/EventID";
import StorageUtil from "./StorageUtil";
import { GlobalData } from "../Common/GlobalData";
import { IBaseSkeData, IFlyResData } from "../Interface/IMessage";
import { SceneGame } from "../SceneGame";
import AudioManager, { ESoundType } from "./AudioManager";

export default class GameUtil {
    private static _instance: GameUtil = null;
    public static get instance() {
        if (this._instance === null) {
            this._instance = new this();
        }
        return this._instance;
    }

    /** 服务器&客户端时间差 */
    private timeOffset: number = 0;

    /** 游戏震屏效果 */
    public static vibration(node: cc.Node, times: number = 1) {
        const distance: number = 2;
        node.stopAllActions();
        cc.tween(node).repeat(
            times,
            cc.sequence(
                cc.moveTo(0.05, cc.v2(0, -distance)), cc.moveTo(0.03, cc.v2(0, 0)),
                cc.moveTo(0.05, cc.v2(-distance, 0)), cc.moveTo(0.03, cc.v2(0, 0)),
                cc.moveTo(0.05, cc.v2(0, distance)), cc.moveTo(0.03, cc.v2(0, 0)),
                cc.moveTo(0.05, cc.v2(distance, 0)), cc.moveTo(0.03, cc.v2(0, 0)),
                cc.callFunc(() => { }),
            )
        ).start();
    }

    /**
     * 打字机效果
     * @param lab 
     * @param str 
     * @param time 
     */
    public static effectWords(lab: cc.Label, str: string, callback?: Function) {
        let arr = str.split("");
        let index = 0;
        lab.string = '';
        lab.node.stopAllActions();
        cc.tween(lab.node).repeat(arr.length,
            cc.sequence(
                cc.callFunc(() => {
                    lab.string = lab.string + arr[index];
                    if (lab.string === str) {
                        callback && callback();
                        callback = null;
                    }
                    index++;
                }),
                cc.delayTime(GlobalData.isFast ? 0.01 : 0.15),
            ),
        ).start();
    }

    /**
     * 检查按钮点击是否在多边形内
     * @param btn 按钮
     * @param e   点击事件
     */
    public static checkCollider(btn: cc.Node, e: cc.Event.EventTouch) {
        const collider = btn.getComponent(cc.PolygonCollider);
        const points = collider.points;
        const local = btn.convertToNodeSpaceAR(e.getLocation());
        const bHit = cc.Intersection.pointInPolygon(local, points);
        return bHit;
    }

    /**
     * 不规则图形碰撞处理
     * @param node 
     * @param rect 
     * @param event 
     * @returns 
     */
    public static checkClicked(node: cc.Node, rect: cc.Node, event: cc.Event.EventTouch) {
        const scale = SceneGame.instance.getRootScale();
        for (let i = 0; i < node.children.length; i++) {
            const item = node.children[i];
            const pos = event.getLocation();
            rect.x = (pos.x - GlobalData.stage_width / 2) / scale;
            rect.y = (pos.y - GlobalData.stage_height / 2) / scale;
            const intersect = GameUtil.intersect(rect, item);
            if (intersect) {
                return true;
            }
        }
        return false;
    }

    /**
     * 矩形相交
     * @param node1 
     * @param node2 
     * @returns 
     */
    public static intersect(node1: cc.Node, node2: cc.Node) {
        if (!node1 || !node2) return false;
        const scale = SceneGame.instance.getRootScale();
        const pos1 = this.localConvertWorldPointAR(node1);
        const pos2 = this.localConvertWorldPointAR(node2);
        if (Math.abs((pos1.x - pos2.x) / scale) < (node1.width + node2.width) / 2 &&
            Math.abs((pos1.y - pos2.y) / scale) < (node1.height + node2.height) / 2) {
            return true;
        }
        return false;
    }

    public static getRandomInteger(a, b) {
        const min = Math.ceil(Math.min(a, b));
        const max = Math.floor(Math.max(a, b));
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    /**
     * 调整现实货币比率
     * 实际提现金额/展示金额 = 1/10000
     * @param value.展示金额
     * @return 实际金额(保留两位小数)
     */
    public static resChangeRatio(value: number): number {
        value /= 10000;
        return Number(value.toFixed(2));
    }

    public static ratioChangeRes(value: number): number {
        value *= 10000;
        return value;
    }

    /**
     * 计量单位组
     * @param value
     */
    private static unitArr: string[] = ["", "K", "M", "B", "T", "P", "E", "Z", "Y"];
    public static theMeasuringUnit(value: number): string {//KMGTPEZY
        let str: string = `${value}`;
        if (value > 9999) {
            for (let i = 0; i < this.unitArr.length; i++) {
                let index: number = i + 1;
                let tempValue: number = value / (Math.pow(1000, index));
                if (tempValue > 1) {
                    const u = this.unitArr[index];
                    str = `${tempValue.toFixed(2)}${u}`;
                    break;
                }
            }
        }
        return str;
    }

    /**
     * 单位转化
     * @param value
     * @param decimal 保留x位小数
     */
    private static charArr: string[] = ["", "万", "亿", "京", "垓", "秭", "穰", "沟", "涧", "正", "载", "极", "恒河沙", "阿僧祗", "那由他", "不可思议", "无量", "大数"];
    public static changeUnit(value: number, decimal: number = 3): string {
        let str: string = `${value}`;
        if (value > 9999) {
            for (let i = 0; i < this.charArr.length; i++) {
                let index: number = i + 1;
                let tempValue: number = value / Math.pow(10000, index);
                if (tempValue < 10000) {
                    const u = this.charArr[index];
                    // const v = Number(tempValue.toFixed(decimal)) * Math.pow(10, decimal) / Math.pow(10, decimal);    // 四舍五入
                    const v = Number(Math.floor(tempValue * Math.pow(10, decimal)) / Math.pow(10, decimal));            // 不四舍五入
                    str = `${v}${u}`;
                    break;
                }
            }
        }
        return str;
    }

    /**
     * 删除小数点后末尾零
     * @param value 
     * @param ratio 系数
     * @param decimal 保留小数(末尾零消除)
     * @returns 
     */
    public static deleteEndZero(value: number, ratio: number, decimal: number = 3): number {
        const temp = (value / ratio).toFixed(decimal);
        const v = Number(temp) * Math.pow(10, decimal) / Math.pow(10, decimal);
        return v;
    }

    /**
     * 图片置灰
     * @param {cc.Node} node
     * @param {boolean} [exceptChild=false] 排除子节点
     * @memberof GameUtil
     */
    public static setGraySprite(node: cc.Node, exceptChild: boolean = false) {
        const func = (parent: cc.Node) => {
            const comp = parent.getComponent(cc.Sprite) || parent.getComponent(cc.Label);
            if (comp) {
                const grayMaterial = cc.Material.getBuiltinMaterial("2d-gray-sprite");
                comp.setMaterial(0, grayMaterial);
                if (parent.getComponent(cc.Label)) {
                    // 引擎 bug, 引擎组提供的方法规避
                    // @ts-ignore
                    comp._forceUpdateRenderData();
                }
            }
            if (!exceptChild) {
                parent.children.forEach((child) => func(child));
            }
        };
        func(node);
    }

    /**
     * 取消置灰
     *
     * @param {cc.Node} node
     * @param {boolean} [exceptChild=false] 排除子节点
     * @memberof GameUtil
     */
    public static setNormalSprite(node: cc.Node, exceptChild: boolean = false) {
        const func = (parent: cc.Node) => {
            const comp = parent.getComponent(cc.Sprite) || parent.getComponent(cc.Label);
            if (comp) {
                const grayMaterial = cc.Material.getBuiltinMaterial("2d-sprite");
                comp.setMaterial(0, grayMaterial);
                if (parent.getComponent(cc.Label)) {
                    // 引擎 bug, 引擎组提供的方法规避
                    // @ts-ignore
                    comp._forceUpdateRenderData();
                }
            }
            if (!exceptChild) {
                parent.children.forEach((child) => func(child));
            }
        };
        func(node);
    }

    /**
     * 获取随机颜色
     */
    public static randomColor(): number {
        //return parseInt("0x" + ("000000" + ((Math.random() * 16777215 + 0.5) >> 0).toString(16))/*.slice(-6)*/);
        let _num: number = Math.floor(Math.random() * 16777215 + 0.5);
        let _string: string = "000000" + _num.toString(16);
        _num = parseInt("0x" + _string);
        return _num;
    }

    public static sleep(ms: number): Promise<void> {
        return new Promise<any>(function (resolve, reject) {
            setTimeout(function () {
                resolve(``);
            }, ms);
        });
    }

    public static async delay(comp: cc.Component, delayMills: number): Promise<void> {
        await new Promise<any>((resolve, reject) => {
            comp.scheduleOnce(() => {
                resolve("");
            }, delayMills / 1000);
        });
    }

    /** 数组中随机取不同元素 */
    public static getRandomDistinct(arr, count) {
        const result = [];
        const used = [];
        while (result.length < count) {
            const index = Math.floor(Math.random() * arr.length);
            if (used.indexOf(index) === -1) {
                result.push(arr[index]);
                used.push(index);
            }
        }
        return result;
    }

    /**
     * 长字符处理
     * @param str 
     * @param maxLength 
     * @returns 
     */
    public static truncateString(str: string, maxLength: number): string {
        if (str.length > maxLength) {
            return str.slice(0, maxLength > 3 ? maxLength - 3 : maxLength) + '...';
        }
        return str;
    }

    /**
     * 从数据中取不同元素
     * @param arr 
     * @param n 
     * @returns 
     */
    public static getRandomElement<T>(arr: T[], n: number): T[] {
        const result = new Array<T>(n);
        const len = arr.length;
        if (n > len) {
            console.log('数组长度不够~');
        }

        for (let i = 0; i < n; i++) {
            const index = Math.floor(Math.random() * arr.length);
            result[i] = arr[index];
            arr.splice(index, 1);
        }
        // console.log("随机:", result);
        return result;
    }

    /**
     * 深拷贝
     * @param obj
     * @returns 
     */
    public static deepCopy(obj) {
        let result = null;
        if (typeof obj === 'object') {
            if (Array.isArray(obj)) {
                result = [];
                for (let i = 0; i < obj.length; i++) {
                    result[i] = this.deepCopy(obj[i]);
                }
            } else {
                result = {};
                for (const key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        result[key] = this.deepCopy(obj[key]);
                    }
                }
            }
        } else {
            result = obj;
        }
        return result;
    }

    /** 解析对象 */
    public static parseObject(obj: any) {
        var keys = [];
        var values = [];
        for (var key in obj) {
            //只遍历对象自身的属性，而不包含继承于原型链上的属性。  
            if (obj.hasOwnProperty(key)) {
                keys.push(key);
                values.push(obj[key]);
            }
        }
        console.log("keys:" + keys + "values:" + values);
    }

    /**
     * 解析Json 字符串分割
     * -@param str
     */
    private static data: number[][];
    public static parseData(str: string): number[][] {
        if (!str) {
            console.log("参数错误");
        } else {
            this.data = [];
            const arr: string[] = str.split("#");
            for (let i = 0; i < arr.length; i++) {
                this.data[i] = [];
                const strArr: string[] = arr[i].split(",");
                for (let j = 0; j < strArr.length; j++) {
                    this.data[i].push(parseInt(strArr[j]));
                }
            }
            return this.data;
        }
    }

    /**
     * 打印节点所在的路径
     * @param node 
     * @returns 
     */
    public getNodePath(node) {
        let path: string = '';
        while (node && !(node instanceof cc.Scene)) {
            if (path) {
                path = node.name + '/' + path;
            }
            else {
                path = node.name;
            }
            node = node.parent;
        }
        return path;
    }


    /**
     * 获取与服务器时间差
     * @param time 服务器时间 "2021-04-20 10:29:37"
     */
    public getTimeOffset(time: number) {
        this.timeOffset = time * 1000 - Date.now();
    }

    /**
     * 获取校正后的时间戳
     *
     * @returns {number}
     * @memberof GameUtil
     */
    public getTime(): number {
        return Date.now() + this.timeOffset;
    }

    /** 求两点之间距离 */
    public getPointDistance(point: cc.Node, point1: cc.Node) {
        const dx: number = point.x - point1.x;
        const dy: number = point.y - point1.y;
        const distance: number = Math.sqrt(dx * dx + dy * dy);
        return distance;
    }

    /**
     * 实现二阶贝塞尔曲线须知三个点点坐标，起点P0(x,y), 控制点P1(x,y),终点P2(x,y)
     * 
     * const a = cc.v2(0, 0);
     * const b = cc.v2(100, 300);
     * const c = cc.v2(200, 0);
     * GameUtil.instance.bezier(this.progressSprite.node, a, b, c);
     */
    public bezier(obj: cc.Node, startPos: cc.Vec2, controlPos: cc.Vec2, endPos: cc.Vec2) {
        cc.tween(obj).bezierTo(
            1.8,
            new cc.Vec2(startPos.x, startPos.y),
            new cc.Vec2(controlPos.x, controlPos.y),
            new cc.Vec2(endPos.x, endPos.y)
        ).call(() => { }).start();
    }

    /**
     * 得到一个节点的世界坐标
     * node的原点在中心
     * @param {*} node 
     */
    public static localConvertWorldPointAR(node) {
        if (node) {
            return node.convertToWorldSpaceAR(cc.v2(0, 0));
        }
        return null;
    }

    /**
     * 把一个世界坐标的点，转换到某个节点下的坐标
     * 原点在node中心
     * @param {*} node 
     * @param {*} worldPoint 
     */
    public static worldConvertLocalPointAR(node, worldPoint) {
        if (node) {
            return node.convertToNodeSpaceAR(worldPoint);
        }
        return null;
    }


    /**
     * 把一个节点的本地坐标转到另一个节点的本地坐标下
     * @param {*} node 
     * @param {*} targetNode 
     */
    public static convetOtherNodeSpaceAR(node, targetNode) {
        if (!node || !targetNode) {
            return null;
        }
        //先转成世界坐标
        let worldPoint = this.localConvertWorldPointAR(node);
        return this.worldConvertLocalPointAR(targetNode, worldPoint);
    }

    /**
     * 飞资源奖励头像 (钞票|红包券)
     * @param icon 资源头像预制体
     * @param resType 飞入资源类型
     * @param parent 父节点
     * @param start
     * @param end
     * @param rewardValue 获得总数量
     */
    public async flyResIcons1(icon: cc.Prefab, resType: EResType, parent: cc.Node, start: cc.Node, end: cc.Node, rewardValue: number) {
        const flyTimes = 6;
        const convet_start = GameUtil.convetOtherNodeSpaceAR(start, parent);
        const convet_end = GameUtil.convetOtherNodeSpaceAR(end, parent);
        for (let i = 0; i < flyTimes; i++) {
            let value = i === flyTimes - 1 ? Math.floor(rewardValue / flyTimes) + rewardValue % flyTimes : Math.floor(rewardValue / flyTimes);
            let node = cc.instantiate(icon);
            node.parent = parent;
            node.setPosition(convet_start);
            node.getComponent(ItemResIcon).refreshIcon(resType);

            const controlPosY = (convet_end.y - convet_start.y) * 0.3;
            cc.tween(node).sequence(
                cc.bezierTo(
                    1.2,
                    [
                        new cc.Vec2(convet_start.x, convet_start.y),
                        new cc.Vec2(convet_end.x, controlPosY),
                        new cc.Vec2(convet_end.x, convet_end.y),
                    ],
                ).easing(cc.easeSineIn()),
                cc.callFunc(() => {
                    node.removeFromParent();

                    // AudioManager.instance.playSound(ESoundType.FlyResRed);
                    this.changeResValue(resType, value);
                    const data = {
                        node: end,
                        resType: resType,
                        value: rewardValue,
                        isEnd: i === flyTimes - 1,
                    };
                    Message.instance.send(EventID.REFRESH_TOP_BAR, data);
                }),
            ).start();
            await GameUtil.sleep(100);
        }
    }

    /**
     * 局部坐标转世界坐标
     * @param node 
     * @returns 
     */
    private localToGlobal(node: cc.Node) {
        const pos = node.convertToWorldSpaceAR(cc.v2(0, 0));
        return pos;
    }
    public flyResIcon(flyData: IFlyResData) {
        try {
            // SceneGame.instance.showTopMask();
            const from = GameUtil.convetOtherNodeSpaceAR(flyData.from, flyData.parent);
            const to = GameUtil.convetOtherNodeSpaceAR(flyData.to, flyData.parent);
            LoadUtil.instance.loadRes(EBundleType.PUBLIC, 'prefab/ItemResIcon', cc.Prefab, async (err, res) => {
                for (let i = 0; i < flyData.flyTimes; i++) {
                    // const reward = i === flyData.flyTimes - 1 ? Math.floor(flyData.value / flyData.flyTimes) + flyData.value % flyData.flyTimes : Math.floor(flyData.value / flyData.flyTimes);
                    const node = cc.instantiate(res) as cc.Node;
                    node.parent = flyData.parent;
                    node.setPosition(cc.v2(from.x, from.y));
                    node.getComponent(ItemResIcon).refreshIcon(flyData.resType, flyData.maxWidth, flyData.maxHeight);

                    const x = node.x + (Math.random() * 300 - 150);
                    const y = node.y + (Math.random() * 300 - 150);
                    cc.tween(node).sequence(
                        cc.moveTo(0.3, cc.v2(x, y)).easing(cc.easeSineIn()),
                        cc.delayTime(0.3 + i * 0.05),
                        cc.moveTo(0.7, cc.v2(to.x, to.y)).easing(cc.easeSineIn()),
                        cc.callFunc(async () => {
                            node.destroy();
                            AudioManager.instance.playSound(ESoundType.ResGold, 0.6);
                            if (i === flyData.flyTimes - 1) {
                                // SceneGame.instance.hideTopMask();
                                this.changeResValue(flyData.resType, flyData.value);
                                Message.instance.send(EventID.REFRESH_TOP_BAR, { type: flyData.resType, value: flyData.value });
                            }
                        }),
                    ).start();
                }
            });
        } catch (error) {
            console.log(error);
        }
    }

    /**
     * 资源&&货币   数值变动
     * @param resType   类型
     * @param value     数值
     */
    public changeResValue(resType: EResType, value: number, isUpdate: boolean = false) {
        const gameData = StorageUtil.instance.getGameData();
        switch (resType) {
            case EResType.Money:
                {
                    gameData.money = PreciseUtil.add(gameData.money, value);
                    // console.log("money:", gameData.money);
                }
                break;
        }
    }

    /**
     * 飞资源动画完成飘字动画(+10086)
     * 需要在节点下放label文本
     * @param add 
     * @param value 
     */
    public moveWordsAnimation(add: cc.Label, value: number, callback: Function/*isCash: boolean = true*/) {
        if (value > 0) {
            const node = cc.instantiate(add.node);
            const label = node.getComponent(cc.Label);
            label.string = `+${GameUtil.changeUnit(value)}`;
            // if (isCash) {
            //     const changeUnit = GameUtil.changeUnit(value);
            //     label.string = `+${changeUnit}`;
            // } else {
            //     const temp = GameUtil.deleteEndZero(value, 1, 4);
            //     label.string = `+${temp}元`;
            // }
            node.parent = add.node.parent;
            cc.tween(node).sequence(
                cc.spawn(
                    cc.moveTo(0, cc.v2(add.node.x, add.node.y)),
                    cc.fadeIn(0),
                ),
                cc.moveTo(1, cc.v2(add.node.x, add.node.y + 60)),
                cc.spawn(
                    cc.moveTo(0, cc.v2(add.node.x, add.node.y)),
                    cc.fadeOut(0),
                ),
                cc.callFunc(() => {
                    node.removeFromParent();
                    callback && callback();
                    callback = null;
                }),
            ).start();
        }
    }

    /** 刷新微信头像 */
    public refreshWxIcon(sprite: cc.Sprite) {
        if (CC_JSB) {
            const url = HttpManager.wxUserInfo.user.headimg;
            cc.assetManager.loadRemote<cc.Texture2D>(url, { ext: '.png' }, (err, pic) => {
                if (err) {
                    console.error("LayerGame.refreshWxIcon()===>err:", err);
                    return;
                }

                const spriteFrame = new cc.SpriteFrame(pic);
                sprite.spriteFrame = spriteFrame;
            });
        }
    }

    /**
     * spine动画封装
     * @param ske 
     * @param animName 
     * @param callback 
     */
    public static playAnimation(data: IBaseSkeData) {
        data.hideBones?.forEach((o) => {
            const bone = data.ske.findBone(o);
            if (bone) {
                bone.active = false;
            }
        });

        data.showBones?.forEach((o) => {
            const bone = data.ske.findBone(o);
            if (bone) {
                bone.active = true;
            }
        });

        if (data.skinName) {
            data.ske.setSkin(data.skinName);
        }

        if (data.slotName && data.attachName) {
            data.ske.setAttachment(data.slotName, data.attachName);
        }

        let curLoop = 0;
        data.ske.timeScale = data.timeScale || 1;
        data.ske.setAnimation(0, data.animName, data.isLoop);
        data.ske.setCompleteListener((trackEntry) => {
            if (trackEntry.animation.name === data.animName) {
                curLoop++;
                if (data.isLoop) {
                    if (data.loopCount && data.loopCount > 1 && curLoop >= data.loopCount) {
                        data.ske.clearTrack(0);
                        data.callback && data.callback();
                        data.callback = null;
                    }
                } else {
                    data.callback && data.callback();
                    data.callback = null;
                }
            }
        });
    }

    /**
     * 根据类型获取资源url
     * @param type
     */
    public getResUrlByType(type: EResType, id?: number | string) {
        let data: { bundle: EBundleType, url: string } = null;
        switch (type) {
            case EResType.Money:
                {
                    data = { bundle: EBundleType.Game, url: 'res/gameui/texture/ui_money' };
                }
                break;
            case EResType.IQ:
                {
                    data = { bundle: EBundleType.Game, url: 'res/gameui/texture/ui_IQ' };
                }
                break;
            case EResType.Item:
                {
                    data = { bundle: EBundleType.Game, url: `res/common/item/item${id}` };
                }
                break;
            case EResType.BubbleIcon:
                {
                    data = { bundle: EBundleType.Game, url: `res/common/texture/${id}` };
                }
                break;
            case EResType.StoryIcon:
                {
                    data = { bundle: EBundleType.Story, url: `res/texture/${id}` };
                }
                break;
            case EResType.HospitalItem:
                {
                    data = { bundle: EBundleType.Game, url: `res/hospital/texture/items/item${id}` };
                }
                break;
            case EResType.HospitalSpine:
                {
                    data = { bundle: EBundleType.Game, url: `res/hospital/spine/${id}` };
                }
                break;
            case EResType.LotteryAnimal:
                {
                    data = { bundle: EBundleType.Game, url: `res/lottery/animal/animal_icon${id}` };
                }
                break;
            case EResType.LotteryFruit:
                {
                    data = { bundle: EBundleType.Game, url: `res/lottery/fruit/fruit${id}` };
                }
                break;
            case EResType.LotteryGress:
                {
                    data = { bundle: EBundleType.Game, url: `res/lottery/grass/grass${id}` };
                }
                break;
            case EResType.LotteryGold:
                {
                    data = { bundle: EBundleType.Game, url: `res/lottery/gold/gold${id}` };
                }
                break;
        }
        return data;
    }
}